package com.sportradar.assessment.work.services;

import java.util.Calendar;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import lombok.Getter;
import lombok.Setter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sportradar.assessment.work.beans.ConfigurationBean;
import com.sportradar.assessment.work.beans.StatisticsBean;
import com.sportradar.assessment.work.util.ConfigUtil;
import com.sportradar.assessment.work.util.Constants;

/*
 * Service class that manage the shared data between threads:
 * keeps and updates the status of 
 * - current configuration, 
 * - current statistics
 * Keeps a reference to the NumberProgression interface
 * 
 * @see NumberProgression
 */
public class ExecutionManager {

	private static Logger logger=LoggerFactory.getLogger(ExecutionManager.class);
	
	/*
	 * Interface for the NumberProgression service
	 */
	private NumberProgression numberProgression;
	
	/*
	 * Bean holding the state of the current configuration
	 */
	@Getter private ConfigurationBean currentConfiguration;
		
	/*
	 * Bean holding the state of the current statistics
	 */
	@Getter private StatisticsBean statistics;

	
	
	public ExecutionManager(NumberProgression numberProgression,ConfigurationBean configuration) {	
		this.numberProgression = numberProgression;
		this.currentConfiguration = configuration;
		this.statistics=new StatisticsBean();
	}
	


	/*
	 * update current settings reading from resources
	 * and log statistics on time of execution's data 
	 * 
	 */
	public void updateSettings(){
		
		Date lastUpdate=this.getStatistics().getLastUpdate();
		if(lastUpdate==null){
			logger.info("First Update");
		}else{
			logger.info("Last Update was in "+lastUpdate);
		}
		
		Calendar nowAsCalendar = Calendar.getInstance();	
		logger.info("Update now: "+nowAsCalendar.getTime());
		this.getStatistics().setLastUpdate(nowAsCalendar.getTime());
		long refreshRateInSeconds=ConfigUtil.getPropertyValueAsLong(Constants.REFRESH_INTERVAL_KEY);
		Calendar nextUpdate =  Calendar.getInstance();	
		nextUpdate.setTime(this.getStatistics().getLastUpdate());
		nextUpdate.add(Calendar.SECOND, (int)refreshRateInSeconds);
		logger.info("next Update will be "+nextUpdate.getTime());
		
		ConfigurationBean newConfiguration=ConfigUtil.update();
		if (!currentConfiguration.getCurrentNumberSequenceThreadName().equals(newConfiguration.getCurrentNumberSequenceThreadName())){
			logger.info("Changed active Thread! New Active ThreadName is "+newConfiguration.getCurrentNumberSequenceThreadName());
		}
		if (!currentConfiguration.getCurrentConfigThreadName().equals(newConfiguration.getCurrentConfigThreadName())){
			logger.info("Changed Config Thread! New Config ThreadName is "+newConfiguration.getCurrentConfigThreadName());
		}
		this.currentConfiguration = newConfiguration;
	}
	
	
	/*
	 * Entry point for thread calls. 
	 * Check the thread name against current configuration settings
	 * and route the calling thread toward its intended method
	 * 
	 * @param threadName current Runnable name
	 * 
	 */
	public void execute(String threadName) throws InterruptedException{
		
		while(true){
			if(threadName.equals(currentConfiguration.getCurrentConfigThreadName())){
				TimeUnit.SECONDS.sleep(currentConfiguration.getIntervalBetweenConfigRefresh());
				updateSettings();			
			}else{
				executeCalculation(threadName);
			}
		}
	}
	
	/*
	 * Method for numeric progression.
	 * Synchronized for potential access from concurrent threads.
	 * 
	 * Checks the thread name against current configuration.
	 * If the thread is not the only one allowed to run, a call to wait() will be performed.
	 * If thread is allowed to run, reads the sleeping interval from current settings and then call
	 * the numeric progression getNextNumber() and updates statistics.
	 * 
	 * @param threadName current Runnable name
	 * @see NumberProgression
	 * 
	 */
	public synchronized void executeCalculation(String threadName) throws InterruptedException{
	
		while(!threadName.equals(currentConfiguration.getCurrentNumberSequenceThreadName())){
				wait();
		}
		
		TimeUnit.SECONDS.sleep(currentConfiguration.getIntervalBetweenCalculations());
		long startComputation = System.nanoTime();
		long nextNumber=numberProgression.getNextNumber();
		long endComputation = System.nanoTime();
			
		long computationSpeed=endComputation-startComputation;
		logger.debug("computationSpeed Found:"+computationSpeed); 		
		logger.info(threadName+ ":"+nextNumber);
		
		this.statistics.incrementComputationSpeed(computationSpeed);
		this.statistics.incrementIterationNumber();
		notifyAll();
		
	}

	
	
}
